package com.hyt.it.ogt.kq.service.gov.service.impl;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.annotation.Resource;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.common.utils.result.Result;
import com.hyt.core.base.BaseServiceImpl;
import com.hyt.core.util.UUIDUtils;
import com.hyt.it.ogt.kq.common.config.ConfigManager;
import com.hyt.it.ogt.kq.common.config.exception.KqException;
import com.hyt.it.ogt.kq.common.gov.constant.ResponseCode;
import com.hyt.it.ogt.kq.common.gov.constant.examsys.TypeEnum;
import com.hyt.it.ogt.kq.common.gov.enums.FileExtensionEnum;
import com.hyt.it.ogt.kq.common.gov.enums.FileTypeEnum;
import com.hyt.it.ogt.kq.common.gov.utils.Constants;
import com.hyt.it.ogt.kq.common.gov.utils.FileUtil;
import com.hyt.it.ogt.kq.common.gov.utils.StringUtil;
import com.hyt.it.ogt.kq.service.gov.feign.ks.KsClient;
import com.hyt.it.ogt.kq.service.gov.feign.ks.model.PushCandidateNoticeParam;
import com.hyt.it.ogt.kq.service.gov.feign.ks.model.PushHelloParam;
import com.hyt.it.ogt.kq.service.gov.feign.tps.GovTpsClient;
import com.hyt.it.ogt.kq.service.gov.mapper.BaseDataPackageMapper;
import com.hyt.it.ogt.kq.service.gov.mapper.ExamAgreementMapper;
import com.hyt.it.ogt.kq.service.gov.model.entity.Agreement;
import com.hyt.it.ogt.kq.service.gov.model.entity.BaseDataPackage;
import com.hyt.it.ogt.kq.service.gov.model.entity.ExamAgreement;
import com.hyt.it.ogt.kq.service.gov.model.entity.SysFile;
import com.hyt.it.ogt.kq.service.gov.model.entity.Task;
import com.hyt.it.ogt.kq.service.gov.model.param.AddExamAgreementParam;
import com.hyt.it.ogt.kq.service.gov.model.vo.ExamAgreementVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.agreement.AgreementVO;
import com.hyt.it.ogt.kq.service.gov.service.IAgreementService;
import com.hyt.it.ogt.kq.service.gov.service.IExamAgreementService;
import com.hyt.it.ogt.kq.service.gov.service.IOfficeExamAgreementService;
import com.hyt.it.ogt.kq.service.gov.service.ISysFileService;
import com.hyt.it.ogt.kq.service.gov.service.ITaskConfigService;
import com.hyt.it.ogt.kq.service.gov.service.ITaskService;
import com.hyt.it.ogt.kq.service.gov.service.ITimeService;
import com.hyt.it.ogt.kq.service.gov.service.obs.CloudObsService;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.model.FileHeader;

/**
 * <p>
 * 考试协议表 服务实现类
 * </p>
 *
 * @author huangyh
 * @since 2021-03-08
 */
@Slf4j
@Service
public class ExamAgreementServiceImpl extends BaseServiceImpl<ExamAgreementMapper, ExamAgreement> implements IExamAgreementService {

    public final static String ENCODE_GBK = "GBK";

    public final static String UTF_8 = "UTF-8";

    private static final int SIZE = 1024 * 1024;

    private static final String CLOUD_PATH_AGREEMENT_PACKAGE = "/agreement/package/";

    private static final String AGREEMENT_EXT = ".html";

    private static final String CLOUD_PATH_AGREEMENT_HTML = "/agreement/html/";

    private static final String CLOUD_PATH_AGREEMENT_IMAGE = "/agreement/image/";

    private final static List<String> OBT_DEFAULT_LIST = Arrays.asList("操作说明.files", "考务要求.htm", "操作说明.htm");

    private final static List<String> GCE_DEFAULT_LIST = Arrays.asList("images", "Affair.html", "Operation.html");

    @Resource
    private ConfigManager configManager;

    @Resource
    private CloudObsService cloudObsService;

    @Resource
    private IAgreementService iAgreementService;

    @Resource
    private ITaskService iTaskService;

    @Resource
    private ISysFileService iFileService;

    @Resource
    private BaseDataPackageMapper baseDataPackageMapper;

    @Resource
    private ExamAgreementMapper examAgreementMapper;

    @Resource
    private KsClient ksClient;

    @Resource
    private IOfficeExamAgreementService iOfficeExamAgreementService;

    @Resource
    private ITimeService iTimeService;

    @Resource
    private GovTpsClient tpsClient;

    @Resource
    private ISysFileService iSysFileService;

    @Resource
    private ITaskConfigService iTaskConfigService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String ids) {
        super.removeByIds(StringUtil.stringToList(ids));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String uploadAgreementFile(MultipartFile multipartFile, String taskId) throws IOException {
        Task task = this.checkTask(taskId);
        File zipFile = new File(StringUtils.joinWith(File.separator, configManager.getAgreementTemp(),
                UUIDUtils.newSortUUID(), multipartFile.getOriginalFilename()));
        zipFile.getParentFile().mkdirs();
        // 备份文件到临时目录，校验用
        FileUtil.writeFile(multipartFile.getInputStream(), zipFile);
        // 校验文件合法性
        this.verifyPackage(zipFile, taskId);
        // 定义obs目录
        String path = StringUtils.join(configManager.getCloudRootPath(), task.getCode(), CLOUD_PATH_AGREEMENT_PACKAGE,
                zipFile.getName());
        // 考试协议包上传到云存储
        String url = cloudObsService.updateFile(path, multipartFile.getInputStream());
        // 记录文件上传信息
        SysFile sysFile = new SysFile();
        sysFile.setName(zipFile.getName()).setPath(path).setUrl(url)
                .setType(FileTypeEnum.FILE_EXAM_AGREEMENT_PACKAGE.getName());
        //插入数据包表
        iFileService.save(sysFile);
        // 删除临时文件目录
        FileUtils.deleteDirectory(zipFile.getParentFile());
        // 返回文件id给前端，前端会带过来
        return sysFile.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteByTaskId(String taskId) {
        this.checkTask(taskId);
        iAgreementService.remove(Wrappers.<Agreement>lambdaQuery().eq(Agreement::getTaskId, taskId));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(AddExamAgreementParam param) {
        LambdaQueryWrapper query = Wrappers.<Agreement>lambdaQuery().eq(Agreement::getTaskId, param.getTaskId());
        boolean flag = iAgreementService.count(query) > 0;
        if (flag) {
            throw new KqException(ResponseCode.AGREEMENT_HAS_EXIST.getCode(), ResponseCode.AGREEMENT_HAS_EXIST.getMsg());
        }
        this.checkTask(param.getTaskId());
        log.info("考试协议包上传路径:{}");
        this.saveData(param);
    }

    private Task checkTask(String taskId) {
        Task task = iTaskService.getById(taskId);
        if (task == null) {
            throw new KqException(ResponseCode.AGREEMENT_TASK_ID_EMPTY.getCode(), ResponseCode.AGREEMENT_TASK_ID_EMPTY.getMsg());
        }
        //: 校验任务数据锁定状态(后续版本考虑优化为从缓存中获取状态)
        iTaskService.isLockDataThrowException(taskId);
        if (this.existBaseDataPackage(taskId)) {
            throw new KqException(ResponseCode.AGREEMENT_PACKAGE_EXIST.getCode(), ResponseCode.AGREEMENT_PACKAGE_EXIST.getMsg());
        }
        return task;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateAgreement(AddExamAgreementParam param) {
        this.checkTask(param.getTaskId());
        Agreement agreement = new Agreement();
        BeanUtils.copyProperties(param, agreement);
        agreement.setUpdateDate(LocalDateTime.now());
        iAgreementService.update(agreement, Wrappers.<Agreement>lambdaUpdate().eq(Agreement::getTaskId,
                param.getTaskId()));
    }

    public void verifyPackage(File agreementPackage, String taskId) {
        //校验导入的考试协议包
        if (!FileExtensionEnum.ZIP.getName().equalsIgnoreCase(
                FilenameUtils.getExtension(agreementPackage.getName()))) {
            throw new KqException(ResponseCode.AGREEMENT_IMPORT_FILE_NOT_ZIP.getCode(),
                    ResponseCode.AGREEMENT_IMPORT_FILE_NOT_ZIP.getMsg());
        }
        if (agreementPackage.length() > (configManager.getExamAgreementFileMaxSize() * SIZE)) {
            throw new KqException(ResponseCode.AGREEMENT_IMPORT_FILE_MAX_SIZE.getCode(),
                    "导入考试协议包文件不超过" + configManager.getExamAgreementFileMaxSize() + "MB");
        }
        Optional<String> optional = iTaskConfigService.getExamSystemTypeByTaskId(taskId);
        boolean pass = optional.map(type -> {
            switch (TypeEnum.getType(type)) {
                case OBT:
                    return this.verityObtPackageContent(agreementPackage);
                case GCE:
                    return this.verifyGcePackageContent(agreementPackage);
                default:
                    return false;
            }
        }).orElseThrow(() -> new KqException(ResponseCode.AGREEMENT_IMPORT_FILE_NOT_ZIP.getCode(),
                "考试系统类型不存在"));
        if (!pass) {
            throw new KqException(ResponseCode.AGREEMENT_IMPORT_FILE_CONTENT_WRONG.getCode(),
                    ResponseCode.AGREEMENT_IMPORT_FILE_CONTENT_WRONG.getMsg());
        }
    }

    /**
     * 基础包是否存在
     *
     * @param taskId
     * @return
     */
    private boolean existBaseDataPackage(String taskId) {
        if (baseDataPackageMapper.selectCount(Wrappers.<BaseDataPackage>lambdaQuery()
                .eq(BaseDataPackage::getTaskId, taskId)) > 0) {
            return true;
        }
        return false;
    }

    /**
     * 证券系统依赖的考试协议文件，要不然考试系统玩不转
     *
     * @param file
     * @return
     * @throws IOException
     */
    @SneakyThrows
    private boolean verityObtPackageContent(File file) {
        //校验考试协议包内容
    	try (ZipFile zipFile = new ZipFile(file)){
    		zipFile.setCharset(Charset.forName("GBK"));
            List<FileHeader> fileHeaders = zipFile.getFileHeaders();
            Map<String, Boolean> map = new HashMap<>(16);
            fileHeaders.stream().forEach(fileHeader -> {
                map.put(new File(fileHeader.getFileName()).getName(), true);
            });
            for (String s : OBT_DEFAULT_LIST) {
                if (map.get(s) == null) {
                    return false;
                }
            }
            return true;
    	} 
    }

    /**
     * 校验GCE考试系统考试协议内容
     * @param file 考试协议包
     * @return 校验结果
     */
    @SneakyThrows
    private boolean verifyGcePackageContent(File file) {
    	try (ZipFile zipFile = new ZipFile(file)){
    		List<FileHeader> fileHeaders = zipFile.getFileHeaders();
    		return GCE_DEFAULT_LIST.stream()
    				.allMatch(defaultName -> fileHeaders.stream()
    						.anyMatch(fileHeader -> defaultName.equals(new File(fileHeader.getFileName()).getName()))
    						);
    	}
    }


    /**
     * 保存考试协议包信息
     *
     * @param param
     */
    private void saveData(AddExamAgreementParam param) {
        Agreement agreement = new Agreement();
        agreement.setFileId(param.getFileId());
        agreement.setUpdateDate(LocalDateTime.now());
        agreement.setName(param.getName());
        agreement.setRemark(param.getRemark());
        try {
            // 新增
            agreement.setTaskId(param.getTaskId());
            agreement.setCreateDate(LocalDateTime.now());
            iAgreementService.save(agreement);
        } catch (DuplicateKeyException e) {
            // 通过主键重复异常，避免重复插入数据，保证一个任务只有一个考试协议包上传
            throw new KqException(ResponseCode.AGREEMENT_HAS_EXIST.getCode(),ResponseCode.AGREEMENT_HAS_EXIST.getMsg());
        }
    }

    @Override
    public List<AgreementVO> getAgreementList(String taskId) {
        return iAgreementService.getAgreementByTaskId(taskId);
    }

    @Override
    public List<ExamAgreement> getExamAgreementList(String taskId) {
        List<ExamAgreement> list = this.lambdaQuery()
                .eq(ExamAgreement::getTaskId, taskId)
                .eq(ExamAgreement::getDelFlag, false)
                .list();

        return list;
    }

    @Override
    public void saveExamNoticeAndWelcomeWords(ExamAgreementVO examAgreementVO) {
        if(examAgreementVO.getType() == 1) {
            String fileName = UUIDUtils.newSortUUID() + AGREEMENT_EXT;
            String path = StringUtils.join(configManager.getCloudRootPath(), examAgreementVO.getTaskId(),
                    CLOUD_PATH_AGREEMENT_HTML, fileName);
            String content = Constants.AGREEMENT_START + examAgreementVO.getContent() + Constants.AGREEMENT_END;
            String res = tpsClient.uploadFileToHuawei("", path, content.getBytes(StandardCharsets.UTF_8));
            Result result = JSON.parseObject(res, Result.class);
            if (result.getSuccess()) {
                examAgreementVO.setContent(result.getObj().toString());
            } else {
                throw new KqException(ResponseCode.ERROR_UPLOAD_AGREEMENT_OBS.getCode(), ResponseCode.ERROR_UPLOAD_AGREEMENT_OBS.getMsg());
            }
            SysFile sysFile = new SysFile();
            sysFile.setName(fileName).setPath(path).setUrl(examAgreementVO.getContent())
                    .setType(FileTypeEnum.FILE_EXAM_AGREEMENT_HTML.getName());
            //插入数据包表
            iFileService.save(sysFile);

            examAgreementVO.setContent(sysFile.getId());
        }
        log.info("保存基本数据参数，{}",JSON.toJSONString(examAgreementVO));
        examAgreementMapper.saveExamNoticeAndWelcomeWords(examAgreementVO);
        //推送数据，验证是否存在批次，如果不存在，则不推送
        if(iTimeService.exists(examAgreementVO.getTaskId())) {
            this.pushNoticeAndWelcomeWords(examAgreementVO);
        }
    }

    @Override
    public List<ExamAgreementVO> getExamNoticeAndWelcomeWordsList(String taskId) {
        List<ExamAgreementVO> list = examAgreementMapper.getExamNoticeAndWelcomeWordsList(taskId);
        for (ExamAgreementVO item : list) {
            if(item.getType() == 1) {
                if(item.getContent() != null) {
                    SysFile sysFile = iFileService.getById(item.getContent());
                    if (sysFile != null) {
                        String res = tpsClient.getStringFromHuaweiObs(sysFile.getPath(), UTF_8);
                        Result result = JSON.parseObject(res, Result.class);
                        if (result.getObj() != null) {
                            Document doc = Jsoup.parse(result.getObj().toString());
                            item.setContent(doc.body().html());
                        } else {
                            throw new KqException(ResponseCode.ERROR_GET_AGREEMENT_FROM_OBS.getCode(), ResponseCode.ERROR_GET_AGREEMENT_FROM_OBS.getMsg());
                        }
                    }
                }
                break;
            }
        }
        return list;
    }

    @Override
    public String uploadAgreementPicture(MultipartFile file, String taskId) throws IOException {
        // 检查文件大小
        if(file.getSize() > configManager.getAgreementUploadFileSize()){
            throw new KqException(ResponseCode.UPLOAD_AGREEMENT_IMAGE_SIZE_LIMIT.getCode(), ResponseCode.UPLOAD_AGREEMENT_IMAGE_SIZE_LIMIT.getMsg());
        }
        String fileExtension = FilenameUtils.getExtension(file.getOriginalFilename()).toLowerCase();
        //检查是否是图片
        if(!configManager.getAgreementUploadFileFormat().contains(fileExtension)){
            throw new KqException(ResponseCode.UPLOAD_AGREEMENT_IMAGE_FORMAT_WRONG.getCode(), ResponseCode.UPLOAD_AGREEMENT_IMAGE_FORMAT_WRONG.getMsg());
        }
        String path = StringUtils.join(configManager.getCloudRootPath(), taskId, CLOUD_PATH_AGREEMENT_IMAGE,
                UUIDUtils.newSortUUID(), ".", fileExtension);
        String res = tpsClient.uploadFileToHuawei("", path, IOUtils.toByteArray(file.getInputStream()));
        Result result = JSON.parseObject(res, Result.class);
        if(result.getSuccess()) {
            return result.getObj().toString();
        } else {
            throw new KqException(ResponseCode.ERROR_UPLOAD_AGREEMENT_IMAGE.getCode(),ResponseCode.ERROR_UPLOAD_AGREEMENT_IMAGE.getMsg());
        }
    }

    /**
     * @Description 推送考生须知和欢迎语到考试系统
     * @Author wangg
     * @date 2021/11/25
     * @Param
     * @Return
     */
    private void pushNoticeAndWelcomeWords(ExamAgreementVO examAgreementVO) {
        String taskId = examAgreementVO.getTaskId();
        String content = examAgreementVO.getContent();
        int type = examAgreementVO.getType();
        if (type == 1) {
            SysFile sysFile = iSysFileService.getSysFileById(content);
            //推送考生须知数据
            PushCandidateNoticeParam pushCandidateNoticeParam = new PushCandidateNoticeParam();
            pushCandidateNoticeParam.setTaskId(taskId);
            pushCandidateNoticeParam.setContent(sysFile.getUrl());
            log.info("推送考生须知参数，{}",JSON.toJSONString(pushCandidateNoticeParam));
            ApiResponse<Object> res = ksClient.pushCandidateNotice(pushCandidateNoticeParam);

            if (res == null) {
                throw new KqException(ResponseCode.AGREEMENT_PUSH_KS_RESULT_NONE.getCode(),ResponseCode.AGREEMENT_PUSH_KS_RESULT_NONE.getMsg());
            }

            if(!ResponseCode.SUCCESS.getCode().equals(res.getCode())) {
                throw new KqException(res.getCode(),"调用考试接口失败。");
            }
        } else if (type == 2) {
            //推送欢迎语数据
            PushHelloParam pushHelloParam = new PushHelloParam();
            pushHelloParam.setTaskId(taskId);
            pushHelloParam.setContent(content);
            log.info("推送欢迎语参数，{}",JSON.toJSONString(pushHelloParam));
            ApiResponse<Object> res = ksClient.pushHello(pushHelloParam);
            if (res == null) {
                throw new KqException(ResponseCode.AGREEMENT_PUSH_KS_RESULT_NONE.getCode(),ResponseCode.AGREEMENT_PUSH_KS_RESULT_NONE.getMsg());
            }
            if(!ResponseCode.SUCCESS.getCode().equals(res.getCode())) {
                throw new KqException(res.getCode(),"调用考试接口失败。");
            }
        }
    }

    @Override
    public void initTaskExamAgreement(String taskId, String officeId) {

        Integer count = lambdaQuery()
                .eq(ExamAgreement::getTaskId,taskId)
                .eq(ExamAgreement::getDelFlag,false)
                .count();

        if (count <= 0 ) {
            //初始化机构数据
            iOfficeExamAgreementService.initOfficeExamAgreement(officeId);
            //初始化任务数据
            baseMapper.initTaskExamAgreement(taskId, officeId);
        }
    }

    @Override
    public void copyTaskExamAgreement(String newTaskId, String oldTaskId, String userName) {
        baseMapper.copyTaskExamAgreement(newTaskId, oldTaskId, userName);
    }

    @Override
    public void existNullExamAgreement(String taskId) {
            Integer count = lambdaQuery()
                    .eq(ExamAgreement::getTaskId,taskId)
                    .eq(ExamAgreement::getDelFlag,false)
                    .isNull(ExamAgreement::getContent)
                    .count();

            if (count > 0) {
                throw new KqException(ResponseCode.AGREEMENT_EDIT_EXIST_AGREEMENT_NULL.getCode(),ResponseCode.AGREEMENT_EDIT_EXIST_AGREEMENT_NULL.getMsg());
            }
    }

    /**
     * 获取考试协议html
     *
     * @param taskId
     * @return
     */
    @Override
    public String getExamAgreement(String taskId) {
        List<ExamAgreementVO> list = examAgreementMapper.getExamNoticeAndWelcomeWordsList(taskId);
        for (ExamAgreementVO item : list) {
            if(item.getType() == 1) {
                if(item.getContent() != null) {
                    SysFile sysFile = iFileService.getById(item.getContent());
                    if (sysFile != null) {
                        String res = tpsClient.getStringFromHuaweiObs(sysFile.getPath(), UTF_8);
                        Result result = JSON.parseObject(res, Result.class);
                        if (result.getObj() != null) {
                            Document doc = Jsoup.parse(result.getObj().toString());
                            return doc.body().html();
                        } else {
                            throw new KqException(ResponseCode.ERROR_GET_AGREEMENT_FROM_OBS.getCode(), ResponseCode.ERROR_GET_AGREEMENT_FROM_OBS.getMsg());
                        }
                    }
                }
                break;
            }
        }
        return null;
    }
}
